前篇-Asp.net使用快取 (一)向大家簡單介紹
HttpRuntime.Cache使用快取機制這篇是分享把快取程式碼變得更有彈性
第二篇大綱
情境:
目前有個專案使用 HttpRuntime.Cache 物件
在記憶體快取中除了使用 Asp.Net 中HttpRuntime.Cache類別外還有很多解決方案.例如使用Memcache,Redis...
如果我們原本使用HttpRuntime.Cache類別但之後要轉成其他快取方式怎麼辦?
public class HomeController : Controller
{
	System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
	public ActionResult Index()
	{
		string cacheData = cacheContainer.Get("data") as string;
		if (cacheData==null)
		{
			cacheContainer.Insert("test1", DateTime.Now.ToShortDateString());
		}
  
		return View(cacheData);
	}
}
雖然使用不同快取方式,但記得我上篇的重點快取會有兩個動作,讀和寫,所以最基本就會有讀和寫這兩個動作
OOP有個很重要的觀念 多個類有重複動作考慮提出父類別
為了方便了解我把HttpRuntime.Cache封裝成一個類別
public class NetCache {
    System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
    public object GetCacheObject(string key) {
        return cacheContainer.Get(key);
    }
    public void SetCache(string key,object obj) {
        cacheContainer.Insert(key, obj);
    }
}
這邊有另一個Memcache快取Class
public class MemeryCache {
	private ObjectCache _cache = MemoryCache.Default;
	public object GetCacheObject(string key)
	{
		return _cache[cacheKey];
	}
	public void SetCache(string key, object obj)
	{
		var policy = new CacheItemPolicy();
		policy.RemovedCallback = OnFileContentsCacheRemove;
		// 設定快取時間2分鐘
		policy.AbsoluteExpiration = DateTimeOffset.Now.Minute(2);
		_cache.Set(cacheKey, fileContents, policy);
	}
}
先不關注這兩個物件裡面細節,我們可以發現他們都有 GetCacheObject 方法和SetCache 方法
這時我們就可以適時提出介面(interface),當作這兩個類別的合約
public interface ICache {
	void Set(string key,object obj);
	object Get(string key);
}
之後將他們兩個類別實現 ICache 介面
public class MemeryCache : ICache
{
	private ObjectCache _cache = MemoryCache.Default;
	public object Get(string key)
	{
		return _cache[cacheKey];
	}
	public void Set(string key, object obj)
	{
		var policy = new CacheItemPolicy();
		policy.RemovedCallback = OnFileContentsCacheRemove;
		// 設定快取時間2分鐘
		policy.AbsoluteExpiration = DateTimeOffset.Now.Minute(2);
		_cache.Set(cacheKey, fileContents, policy);
	}
}
public class NetCache : ICache
{
    System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
    public object Get(string key) {
        return cacheContainer.Get(key);
    }
    
    public void Set(string key,object obj) {
        cacheContainer.Insert(key, obj);
    }
}
提出介面有甚麼好處?
我們可以把前面程式碼改成IOC依賴注入的方式,不要在程式碼寫死使用HttpRuntime.Cache,由IOC容器幫我們把物件注入程式碼中.
Note:我使用建構子注入法
public class HomeController : Controller
{
    //不用寫死使用  HttpRuntime.Cache
	//System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
    ICache cacheContainer;
    public HomeController(ICache Container){
        cacheContainer = Container;
    }
    
	public ActionResult Index()
	{
		string cacheData = cacheContainer.Get("data") as string;
		if (cacheData==null)
		{
			cacheContainer.Insert("test1", DateTime.Now.ToShortDateString());
		}
  
		return View(cacheData);
	}
}
ICache 變成快取程式碼的潤滑劑.可讓程式變得更有彈性
我在StackOverFlow解答的方式就是第二種
其中最主要的技巧就是把Get方法返回的Object改成使用泛型
 public T GetOrSetCache<T>
    (string key,T obj, int cacheTime) where T:class,new()
{
    System.Web.Caching.Cache cacheContainer = HttpRuntime.Cache;
    T cacheObj = cacheContainer.Get(key) as T;
    if (cacheObj == null)
    {
        cacheContainer.Insert(key,
            obj,
            null, 
            DateTime.Now.AddMinutes(cacheTime),
            System.Web.Caching.Cache.NoSlidingExpiration);
        cacheObj = obj;
    }
    return cacheObj;
}
讓我們在使用時可以變成
var data = DateTime.Now.ToShortDateString();
int numberOfMinutes = 3;
data = GetOrSetCache("name1",data,numberOfMinutes );
我們只需要呼叫GetOrSetCache方法,這個方法把GetCache和SetCache封裝起來了
.Net有提供一個很方便的機制 擴充方法,這個機制幫我們解決一個很重要的問題.
我們可以擴充已經封裝但沒有原始碼的類別,
在這段程式碼中,使用Func<TObj> 可以使用lambda 表達式,讓程式碼更簡潔有力!!
public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime)    where TObj : class
{ 
	Cache cacheContainer = HttpRuntime.Cache;
	//get cache Object
	var obj = cacheContainer.Get(key) as TObj;
	//if there isn't cache object add this object to cache
	if (obj == null)
	{
		obj = selector();
		cacheContainer.Insert(key, obj);
	}
	return obj;
}
我們使用時如下
變更簡潔動作更漂亮
int numberOfMinutes = 3;
data = GetOrSetCache(()=> DateTime.Now.ToShortDateString(),"name1",data,numberOfMinutes );
同場加映:
擴展方法和介面搭配使用
public class WebDefaultCache : ICache
{
	Cache cacheContainer = HttpRuntime.Cache;
	public object Get(string key)
	{
		return cacheContainer.Get(key);
	}
	public void Set(string key, object obj)
	{
		cacheContainer.Insert(key, obj);
	}
}
public interface ICache{
	void Set(string key, object obj);
	object Get(string key);
}
public static class InfrastructureExtension
{
	public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key) where TObj : class {
		return GetOrSetCache(selector, key,10);
	}
	public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime) where TObj : class
	{
		return GetOrSetCache(selector, key, cacheTime, new WebDefaultCache());
	}
	public static TObj GetOrSetCache<TObj>(this Func<TObj> selector, string key, int cacheTime, ICache cacheContainer) where TObj : class
	{
		//get cache Object
		var obj = cacheContainer.Get(key) as TObj;
		//if there isn't cache object add this object to cache
		if (obj == null)
		{
			obj = selector();
			cacheContainer.Set(key, obj);
		}
		return obj;
	}
}
雖然在使用上和第三種一樣
但我們多了使用方法重載多傳一個參數ICache介面 可以讓我們在寫程式時決定要使用哪種cache方式,不用改快去那邊程式碼.
同場加映程式碼我放在我自己常用的ExtenionTool專案中